#ifndef     __CODEC_H
#define     __CODEC_H

#include "./encode.h"
#include "./decode.h"
#include "./error.h"
#include <utility>
#include "./proto.h"
#include "./alg.h"

namespace utils {

template <typename T, typename... Ts>
inline constexpr bool is_static_type() {
    using Type = std::decay_t<T>;
    if constexpr (std::is_integral_v<Type> || std::is_enum_v<Type>) {
        if constexpr (sizeof...(Ts) != 0) {
            return is_static_type<Ts...>();
        } else {
            return true;
        }
    } else {
        return false;
    }
}

template <typename T, typename... Ts>
inline constexpr uint32_t get_static_size() {
    using Type = std::decay_t<T>;
    if constexpr (sizeof...(Ts) != 0) {
        return sizeof(Type) + get_static_size<Ts...>();
    } else {
        return sizeof(Type);
    }
}

inline uint32_t encode(uint8_t *) {
    return 0;
}

template <typename T, typename... Ts>
inline uint32_t encode(uint8_t *buf, T&& val, Ts&&... ts) {
    using Type = std::decay_t<T>;
    if constexpr ( is_static_type<T>() ) {
        constexpr uint32_t len = sizeof (Type);
        encode_any<Type, len>(buf, val);
        if constexpr (sizeof...(Ts) != 0) {
            return len + encode(buf + len, std::forward<Ts>(ts)...);
        } else {
            return len;
        }
    } else {
        uint32_t len = val.encode(buf);
        if constexpr (sizeof...(Ts) != 0) {
            return len + encode(buf + len, std::forward<Ts>(ts)...);
        } else {
            return len;
        }
    }
}

template <typename T, typename... Ts>
inline void decode_static(const uint8_t *buf, T&& val, Ts&&... ts) {
    using Type = std::decay_t<T>;
    constexpr uint32_t len = sizeof (Type);
    val = decode_any<Type, len>(buf);
    if constexpr (sizeof...(Ts) != 0) {
        decode_static(buf + len, std::forward<Ts>(ts)...);
    }
}

template <typename T, typename... Ts>
inline bool decode_dyn(const uint8_t *buf, uint32_t size, T&& val, Ts&&... ts) {
    using Type = std::decay_t<T>;

    if constexpr ( is_static_type<T>() ) {
        constexpr uint32_t len = sizeof (Type);
        if (size < len) {
            return false;
        }
        val = decode_any<Type, len>(buf);
        if constexpr (sizeof...(Ts) != 0) {
            return decode_dyn(buf + len, size - len, std::forward<Ts>(ts)...);
        } else {
            return true;
        }
    } else {
        uint32_t len = Type::decode(buf, size, val);
        if (len == DecodeFail) {
            return false;
        }
        if constexpr (sizeof...(Ts) != 0) {
            return decode_dyn(buf + len, size - len, std::forward<Ts>(ts)...);
        } else {
            return true;
        }
    }
}

template <typename... Ts>
inline error_t decode(const uint8_t *buf, uint32_t size, Ts&&... ts) {
    if constexpr ( is_static_type<Ts...>() ) {
        constexpr uint32_t all_size = get_static_size<Ts...>();
        if (all_size > size) {
            return ec::ParseFail;
        }
        decode_static(buf, std::forward<Ts>(ts)...);
        return ec::Ok;
    } else {
        if (decode_dyn(buf, size, std::forward<Ts>(ts)...)) {
            return ec::Ok;
        }
        return ec::ParseFail;
    }
}

template <typename... Ts>
inline uint32_t make_proto(uint8_t *buf, uint16_t req, Ts&&... ts) {
    encode_uint16(buf, proto::Head);
    encode_uint16(buf + 4, req | proto::SalveFlag);
    uint32_t len = encode(buf + 6, std::forward<Ts>(ts)...);
    uint8_t s = alg::sun_xor(buf + 6, len);
    encode_uint8(buf + 6 + len, s);
    encode_uint8(buf + 7 + len, proto::End);
    encode_uint16(buf + 2, len + 8);
    return 8 + len;
}


}

#endif
